/*
 * Driver for tucsen truechromeIII load fpga program
 *
 * Copyright (C) 2017, Fuzhou(ChenduDu) Tucsen Co.,ltd.
 * Copyright 2017 by gszqy
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License version 2 as
 * published by the Free Software Foundation.
 */
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/err.h>
#include <linux/gpio.h>
#include <linux/io.h>
#include <linux/of.h>
#include <linux/of_gpio.h>
#include <linux/interrupt.h>
#include <linux/platform_device.h>
#include <linux/timer.h>

static struct timer_list load_fpga_timer;

enum DOWNLOAD_PIN_DIR {
	PIN_DIR_IN = 0,
	PIN_DIR_OUT = 1,
};
struct download_pins_desc {
	char *name;
	enum DOWNLOAD_PIN_DIR eDir;
	void *handler;
};


static irqreturn_t gpio_init_handler(int irq, void *dev_id)
{
    printk("Enter gpio_init_handler!\n");
	return IRQ_HANDLED;
}

static irqreturn_t gpio_done_handler(int irq, void *dev_id)
{
    printk("Enter gpio_done_handler!\n");
	return IRQ_HANDLED;
}


static struct download_pins_desc pins_desc[] = {
	{"gpio-program", PIN_DIR_OUT, NULL},
	{"gpio-clk", PIN_DIR_OUT, NULL},
	{"gpio-din", PIN_DIR_OUT, NULL},
	
	{"gpio-init", PIN_DIR_IN, gpio_init_handler},
	{"gpio-done", PIN_DIR_IN, gpio_done_handler},
};



struct download_pin_desc {
	char *name;
	int gpio_num;
	int gpio_value;
};
struct load_fpga_info {
	struct download_pin_desc pins[ARRAY_SIZE(pins_desc)];
};

static void load_fpga_timer_function(unsigned long data)
{
	int i = 0;
	struct load_fpga_info *info = (void *)data;

	for (i = 0; i < ARRAY_SIZE(pins_desc) - 2; i++)
	{
		info->pins[i].gpio_value = !(info->pins[i].gpio_value);
		//printk(KERN_EMERG "name = %s, value = %d\n", info->name, info->gpio_value);
		gpio_set_value(info->pins[i].gpio_num, info->pins[i].gpio_value);
	}
	mod_timer(&load_fpga_timer, jiffies+HZ/100);
}


static int load_fpga_probe(struct platform_device *pdev)
{
	int ret, i;
	struct load_fpga_info *info;
	struct device_node *np = pdev->dev.of_node;
	int gpio;
	enum of_gpio_flags flag;

	
	printk(KERN_EMERG "load_fpga_probe!\n");
	info = devm_kzalloc(&pdev->dev, sizeof(struct load_fpga_info), GFP_KERNEL);
	if (!info) {
        dev_err(&pdev->dev, "devm_kzalloc failed!\n");
		return -ENOMEM;
	}

	for (i = 0; i < ARRAY_SIZE(pins_desc); i++)
	{
		gpio = of_get_named_gpio_flags(np, pins_desc[i].name, 0, &flag);
	    if (!gpio_is_valid(gpio)) {
	        dev_err(&pdev->dev, "%s: %d is invalid\n", pins_desc[i].name, gpio);
	        return -ENODEV;
	    }

	    ret = devm_gpio_request(&pdev->dev, gpio, pins_desc[i].name);
		if (ret) {
			dev_err(&pdev->dev, "%s: %d request failed!\n", pins_desc[i].name, gpio);
			return ret;
		}
		
	    info->pins[i].name = pins_desc[i].name;
	    info->pins[i].gpio_num = gpio;
		if (pins_desc[i].eDir == PIN_DIR_IN)
		{
			info->pins[i].gpio_value = flag;
			gpio_direction_input(info->pins[i].gpio_num);
			ret = request_irq(info->pins[i].gpio_num, pins_desc[i].handler, IRQF_SHARED, pins_desc[i].name, &(info->pins[i]));
			if (ret)
			{
				free_irq(info->pins[i].gpio_num, &(info->pins[i]));
		    	dev_err(&pdev->dev, "Failed to request IRQ: %d\n", ret);
			}
			continue;
		}
	    
	    info->pins[i].gpio_value = (flag == OF_GPIO_ACTIVE_LOW) ? 0:1;
	    gpio_direction_output(info->pins[i].gpio_num, info->pins[i].gpio_value);
	}

	platform_set_drvdata(pdev, info);
	init_timer(&load_fpga_timer);
	load_fpga_timer.expires = jiffies+HZ/100;
	load_fpga_timer.data = (unsigned long)info;
	load_fpga_timer.function = load_fpga_timer_function;
	add_timer(&load_fpga_timer);

	return 0;
}

static int load_fpga_remove(struct platform_device *pdev)
{
	int i = 0;
	struct load_fpga_info *info = platform_get_drvdata(pdev);

	//printk(KERN_EMERG "%d\n", ARRAY_SIZE(info));
	for (i = 0; i < ARRAY_SIZE(pins_desc); i++)
	{	
		if (pins_desc[i].eDir == PIN_DIR_IN)
		{
			free_irq(info->pins[i].gpio_num, &(info->pins[i]));
		}
		gpio_free(info->pins[i].gpio_num);
	}
	
	del_timer(&load_fpga_timer);
	printk(KERN_EMERG "load_fpga_remove!\n");
	return 0;
}


static struct of_device_id load_fpga_match_table[] = {
	{.compatible = "tucsen,truechrome-load_fpga",},
	{},
};

static struct platform_driver load_fpga_driver = {
	.driver = {
		.name = "load_fpga",
		.owner = THIS_MODULE,
		.of_match_table = load_fpga_match_table,
	},
	.probe = load_fpga_probe,
	.remove = load_fpga_remove,
	
};

 static int __init load_fpga_init(void)
 {
 	return platform_driver_register(&load_fpga_driver);
 }

 static void __exit load_fpga_exit(void)
 {
 	platform_driver_unregister(&load_fpga_driver);
 }

 module_init(load_fpga_init);
 module_exit(load_fpga_exit);

 
 MODULE_AUTHOR("gszqy <gszqy@163.com>");
 MODULE_DESCRIPTION("load fpga driver");
 MODULE_ALIAS("platform:load fpga");
 MODULE_LICENSE("GPL");

